package com.xdxc.utils;

import com.xdxc.feign.AuthClient;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.sql.Timestamp;
import java.time.Duration;
import java.util.Map;

@Slf4j
@Component
public class TokenManager {

    private static final String CACHE_KEY = "service_token_%s";
    /** 提前刷新阈值（秒） */
    private static final long REFRESH_MARGIN = 5 * 60;
    /** 老 token 最长可容忍时长（秒），当刷新失败时可继续使用旧 token */
    private static final long OLD_TOKEN_GRACE = 60;

    @Value("${spring.security.oauth2.resource-server.opaque-token.client-id}")
    private String clientId;
    @Value("${spring.security.oauth2.resource-server.opaque-token.client-secret}")
    private String clientSecret;

    private final StringRedisTemplate redisTemplate;
    private final AuthClient authClient;

    public TokenManager(StringRedisTemplate redisTemplate, AuthClient authClient) {
        this.redisTemplate = redisTemplate;
        this.authClient = authClient;
    }

    public String getToken() {
        String cacheKey = String.format(CACHE_KEY, clientId);

        // 1. 先读缓存
        String token = redisTemplate.opsForValue().get(cacheKey);
        if (StringUtils.hasText(token) && !shouldRefresh(token)) {
            log.debug("使用缓存 token，剩余有效期充足");
            return token;
        }

        // 2. 双重检查锁，避免多线程同时刷新
        synchronized ((cacheKey).intern()) {
            // 再次检查缓存
            token = redisTemplate.opsForValue().get(cacheKey);
            if (StringUtils.hasText(token) && !shouldRefresh(token)) {
                log.debug("锁内复用缓存 token");
                return token;
            }

            // 3. 尝试刷新
            try {
                log.info("开始刷新服务 token");
                String timestamp = String.valueOf(System.currentTimeMillis());
                String data = clientId + "|" + timestamp;
                String signature = HmacUtil.sign(data, clientSecret);  // 生成签名

                log.info("开始刷新服务 token");
                Map<String, Object> resp = authClient.getToken(clientId, timestamp, signature);

                String newToken = (String) resp.get("access_token");
                Integer expiresIn = (Integer) resp.get("expires_in");
                if (!StringUtils.hasText(newToken) || expiresIn == null) {
                    throw new IllegalStateException("auth-service 未返回合法 token 或 expires_in");
                }

                // 4. 缓存：设置 TTL = expiresIn - REFRESH_MARGIN
                long ttl = Math.max(expiresIn - REFRESH_MARGIN, OLD_TOKEN_GRACE);
                redisTemplate.opsForValue().set(cacheKey, newToken, Duration.ofSeconds(ttl));
                log.info("刷新并缓存新 token，ttl={} 秒", ttl);
                return newToken;

            } catch (Exception e) {
                log.error("刷新服务 token 失败: {}", e.getMessage(), e);
                // 刷新失败：若旧 token 尚未过期超过 OLD_TOKEN_GRACE，可继续使用
                if (StringUtils.hasText(token) && !isExpiredBeyondGrace(token)) {
                    log.warn("使用旧 token（刷新失败容错）");
                    return token;
                }
                // 无旧 token 或已过度老化，抛出异常
                throw new RuntimeException("无法获取服务 token 且无有效旧 token", e);
            }
        }
    }

    /** 判断当前 token 距离过期是否需要刷新 */
    private boolean shouldRefresh(String token) {
        try {
            Claims claims = JWTUtil.extractAllClaims(token);
            long expSec = 0;
            if (claims != null) {
                expSec = claims.getExpiration().getTime() / 1000;
            }
            long nowSec = System.currentTimeMillis() / 1000;
            long remain = expSec - nowSec;
            log.debug("token 剩余秒数：{}", remain);
            return remain <= REFRESH_MARGIN;
        } catch (Exception e) {
            log.warn("解析缓存 token 失败，触发刷新", e);
            return true;
        }
    }

    /** 判断旧 token 是否在可用宽限期之外 */
    private boolean isExpiredBeyondGrace(String token) {
        try {
            Claims claims = JWTUtil.extractAllClaims(token);
            long expSec = 0;
            if (claims != null) {
                expSec = claims.getExpiration().getTime() / 1000;
            }
            long nowSec = System.currentTimeMillis() / 1000;
            return nowSec - expSec > OLD_TOKEN_GRACE;
        } catch (Exception e) {
            // 无法解析则认为已彻底过期
            return true;
        }
    }
}
